# -*- coding:utf-8 -*-
import os
import sys
import getopt
from concurrent.futures import ThreadPoolExecutor, as_completed
from utils.util_logfile import nlogger, traceback
from utils.util_xlsx import HandleXLSX
from utils.util_identitycard import IdentityCard
from datetime import datetime
from settings import BASE_URL, TASK_WAITING_TIME, MAX_WORKERS, HEADER
from row_object import RowStatus
import time
import random
import settings


class GetURLError(Exception):
    pass


class GetRowIterError(Exception):
    pass


class GetStorageError(Exception):
    pass


class GetValidateError(Exception):
    pass


class DownloadVideoError(Exception):
    pass


class ThreadTaskError(Exception):
    pass


class WriteResultError(Exception):
    pass


def exec_func(file_name, sheet_name=None, start_point=None, end_point=None, **kwargs):
    """
    Executive Function
    :param file_name: Excel file name
    :param sheet_name: sheet name, default active sheet
    :param start_point: start row number, minimum is 2 ( row 1 is column name)
    :param end_point: end row number , maximum is the row number of sheet
    :param kwargs:
    :return:
    """
    try:
        _file_name = check_file_name(file_name, **kwargs)
        _source_xls, _row_object_iterator = get_row_object_iterator(_file_name, sheet_name, start_point, end_point,
                                                                    **kwargs)
        # return _source_xls, _row_object_iterator
        _validate_result = validate_thread(_row_object_iterator, **kwargs)
        # return _validate_result
        write_result_to_xls(_source_xls, _validate_result)
    except (GetURLError, GetRowIterError, GetStorageError, DownloadVideoError, ThreadTaskError, WriteResultError) as e:
        nlogger.error('{fn} Custom error: {e}'.format(fn='exec_func', e=repr(e)))
        print(f'Custom error: {repr(e)}')
    except AssertionError as e:
        nlogger.error('{fn} Assertion error: {e}'.format(fn='exec_func', e=repr(e)))
        print(repr(e))
    except Exception as e:
        nlogger.error('{fn} error: {e}'.format(fn='exec_func', e=traceback.format_exc()))
        print(f'Undefined error: {repr(e)}')


def check_file_name(file_name, **kwargs):
    """
    check if the file exists
    :param file_name:
    :param kwargs:
    :return:
    """
    assert file_name is not None, "Parameter file_name must be provided and is not None."
    _file_name = str(file_name).strip()
    assert os.path.exists(_file_name), "file_name {f} does not exists".format(f=_file_name)
    return _file_name


def get_row_object_iterator(file_name, sheet_name=None, start_point=None, end_point=None, **kwargs):
    """
    get iterator of row object
    :param file_name:
    :param sheet_name:
    :param start_point:
    :param end_point:
    :param kwargs:
    :return: instance of HandleXLSX object, iterator of row object
    """
    try:
        source_xls = HandleXLSX(file_name, sheet_name)
        row_object_iterator = source_xls.generate_row_object_iterator(sheet_name, start_point, end_point, **kwargs)
        return source_xls, row_object_iterator
    except Exception as e:
        nlogger.error('{fn} error: {e}'.format(fn='get_row_object_iterator', e=traceback.format_exc()))
        raise GetRowIterError('{fn} error: {e}'.format(fn='get_row_object_iterator', e=repr(e)))


def validate_thread(row_object_iterator, **kwargs):
    """
    validate_thread by multi Thread
    :param row_object_iterator:
    :param kwargs:
    :return: The result of validate, it's list
    """
    try:
        validate_result = []
        executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)
        all_task = [executor.submit(verify_id_task, row_object) for row_object in row_object_iterator]

        for future in as_completed(all_task, timeout=TASK_WAITING_TIME):
            data = future.result()
            if data:
                validate_result.append(data)

        return validate_result
    except Exception as e:
        nlogger.error("{fn} error: {e}".format(fn='validate_thread', e=traceback.format_exc()))
        raise ThreadTaskError('{fn} error: {e}'.format(fn='validate_thread', e=repr(e)))


def verify_id_task(row_object, **kwargs):
    """
    Verify identity task
    :param row_object: row object
    :param kwargs:
    :return:  The Row object that processed by task
    """
    try:
        check_row_object(row_object, **kwargs)
        _row_object = get_validate_result(row_object, **kwargs)
        return _row_object
    except AssertionError as e:
        row_object.status = RowStatus.ERROR.value
        nlogger.error("{fn} Params error: {e}".format(fn='validate_admission_ticket_task', e=traceback.format_exc()))
        if hasattr(row_object, 'column_value') and isinstance(row_object.column_value, dict):
            row_object.column_value['result'] = f'Params AssertionError: {str(e)}'
        else:
            setattr(row_object, 'column_value', {'result': f'Params AssertionError: {str(e)}'})
        return row_object
    except GetURLError as e:
        row_object.status = RowStatus.ERROR.value
        row_object.column_value['result'] = f'GetURLError: {str(e)}'
        return row_object
    except GetStorageError as e:
        row_object.status = RowStatus.ERROR.value
        row_object.column_value['result'] = f'GetStorageError: {str(e)}'
        return row_object
    except GetValidateError as e:
        row_object.status = RowStatus.ERROR.value
        row_object.column_value['result'] = f'GetValidateError: {str(e)}'
        return row_object
    except DownloadVideoError as e:
        row_object.status = RowStatus.ERROR.value
        row_object.column_value['result'] = f'DownloadVideoError: {str(e)}'
        return row_object
    except Exception as e:
        row_object.status = RowStatus.ERROR.value
        nlogger.error("{fn} sn:{s}, vid:{v}, undefined error: {e}".format(fn='validate_admission_ticket_task',
                                                                          s=row_object.column_value['sn'],
                                                                          v=row_object.column_value['vid'],
                                                                          e=traceback.format_exc(), ))
        row_object.column_value['result'] = f'validate_admission_ticket_task undefined error: {str(e)}'
        return row_object


def check_row_object(row_object, **kwargs):
    """
    check Row object property
    :param row_object:
    :param kwargs:
    :return:
    """
    assert isinstance(row_object.file_name, str) and str(
        row_object.file_name).strip(), "Invalid parameter file_name: {p}".format(p=str(row_object.file_name).strip())
    assert isinstance(row_object.sheet_name, str) and str(
        row_object.sheet_name).strip(), "Invalid parameter sheet_name: {p}".format(p=str(row_object.sheet_name).strip())
    assert row_object.column_name and isinstance(row_object.column_name,
                                                 (list, tuple)), "Parameter column_name is list and not be empty"
    assert row_object.row_value and isinstance(row_object.row_value,
                                               (list, tuple)), "Parameter row_value is list and not be empty"
    assert row_object.column_value and isinstance(row_object.column_value,
                                                  dict), "Parameter column_value is dict and not be empty"
    assert row_object.position and isinstance(row_object.position,
                                              int), "Parameter position is int and not be less than 1"
    assert row_object.status and RowStatus(row_object.status).name == 'INITIAL', "Parameter status is invalid"


def validate_identity(row_object, **kwargs):
    try:
        _id = row_object.column_value.get('证件号码')
        assert _id, "parameter identity is not null"

        inst = IdentityCard(id_number=_id)
        row_object.column_value['result'] = "有效"
        _remarks_list = [inst.id_number, inst.province, inst.city, inst.county, inst.birthday, inst.police, inst.gender,
                         inst.verification_code]
        _remarks_string = '|'.join('{s}'.format(s=i) for i in _remarks_list)
        # print(inst1.id_number)
        # print(inst1.province)
        # print(inst1.city)
        # print(inst1.county)
        # print(inst1.birthday)
        # print(inst1.police)
        # print(inst1.gender)
        # print(inst1.verification_code)
        row_object.column_value['remarks'] = _remarks_string
    except Exception as e:
        row_object.column_value['result'] = "无效"
        row_object.column_value['remarks'] = str(e)
    finally:
        return row_object


def get_validate_result(row_object, **kwargs):
    """
    Get validate_result
    :param row_object:
    :param kwargs:
    :return:
    """
    try:
        _row_object = validate_identity(row_object, **kwargs)

        if row_object.column_value['result'] == "有效":
            row_object.status = RowStatus.SUCCESS.value
        else:
            row_object.status = RowStatus.FAILURE.value

        return row_object

    except Exception as e:
        nlogger.error("{fn} error: {e}".format(fn='get_validate_result', e=traceback.format_exc()))
        raise GetValidateError("{fn} error: {e}".format(fn='get_validate_result', e=repr(e)))


def write_result_to_xls(source_xls, validate_result):
    """
    Write result in sheet of Excel
    :param source_xls:
    :param validate_result:
    :return:
    """
    try:
        column_name_list = source_xls.get_column_name_list()
        for row_object in validate_result:
            x = row_object.position
            if 'result' in column_name_list:
                y = column_name_list.index('result') + 1
            else:
                y = len(column_name_list)+1
            result_values = (x, y, row_object.column_value.get('result', 'unknown'))
            source_xls.write_sheet_rows_value(sheet_name=row_object.sheet_name, values=result_values)

            remarks_values = (x, y + 1, row_object.column_value.get('remarks', 'unknown'))
            source_xls.write_sheet_rows_value(sheet_name=row_object.sheet_name, values=remarks_values)

        source_xls.save("validate_{d}.xlsx".format(d=datetime.now().strftime('%Y%m%d-%H:%M:%S')))
    except Exception as e:
        nlogger.error("{fn} error: {e}".format(fn='write_result_to_xls', e=traceback.format_exc()))
        raise WriteResultError("{fn} error: {e}".format(fn='write_result_to_xls', e=repr(e)))


def usage():
    """
    Command help
    :return:
    """
    info = \
        """
Usage:
    python3.9 handle.py -file [ -sheet -start -end ]
    Help:
     -h --help   
     
    Mandatory options:
     -f --file    <source excel>
    
    Optional options:
     -t --sheet   <The sheet name in Excel, default active sheet in Excel>
     -s --start   <Excel start row. Must be int,default index 2>
     -e --end     <Excel end row. Must be int,default Maximum number of rows in Excel>
        """
    print(info)


def main(argv):
    """
    Entrance function
    :param argv: command parameters
    :return:
    """
    _file_name = None
    _sheet_name = None
    _start_point = None
    _end_point = None

    try:
        opts, args = getopt.getopt(argv, "hf:t:s:e:", ["help", "file", "sheet", "start", "end"])  # 短选项模式
    except getopt.GetoptError:
        print("Usage 1: python3.9 handle.py -h  -f <source excel>  -t <excel sheet>  -s <start row index>  "
              "-e <end row index> \nUsage 2: python3.9 handle.py --help  --file <source excel>  --sheet <excel sheet>  "
              "--start <start row index>  --end <end row index>")
        sys.exit(2)  # 2 Incorrect Usage

    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage()
            sys.exit(0)
        elif opt in ('-f', '--file'):
            _file_name = str(arg).strip()
        elif opt in ('-t', '--sheet'):
            _sheet_name = str(arg).strip()
        elif opt in ('-s', '--start'):
            _start_point = str(arg).strip()
        elif opt in ('-e', '--end'):
            _end_point = str(arg).strip()

    if _file_name is None:
        print("Invalid parameter, -f --file must be provided. \nTry '-h --help' for more information.")
        sys.exit(2)

    if _start_point is not None and not _start_point.isdigit():
        print("Invalid parameter, -s --start must be followed by integer. \nTry '-h --help' for more information.")
        sys.exit(2)

    if _end_point is not None and not _end_point.isdigit():
        print("Invalid parameter, -e --end must be followed by integer. \nTry '-h --help' for more information.")
        sys.exit(2)

    params = dict(file_name=_file_name, sheet_name=_sheet_name, start_point=_start_point, end_point=_end_point)

    exec_func(**params)


if __name__ == "__main__":
    # file_name = '制式培训报名记录20221223.xlsx'
    file_name = 'ydcx2023.xlsx'
    sheet_name = 'sheet1'
    start_point = 2
    end_point = None
    kwargs = dict()
    exec_func(file_name, sheet_name=sheet_name, start_point=2, end_point=end_point, **kwargs)

# print("validate_result: ", validate_result)
# # print("_row_object_iterator: ", _row_object_iterator)
#
# for i in validate_result:
#     print(i)

# main(sys.argv[1:])

# python3.9 handle.py --file 'vid-20210214.xlsx' --sheet 'listing' --start 2 --end 4
# python3.9 handle.py -f 'vid-20210214.xlsx' -t 'listing' -s 2 -e 4

# print("***" * 30)
# print(f'pre_row_object: {pre_row_object}', type(pre_row_object))
# print(f'root_path: {root_path}', type(root_path))
# print(f'force_download: {force_download}', type(force_download))
# print("***" * 30)
